#pragma once

#include <cstdint>
#include <vector>
#include <fstream>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>

#include "nlohmann/json.hpp"

enum MeshAttribute : uint16_t
{
    GLB_ATTR_NO_ATTR,
    GLB_ATTR_INDICIES,

    GLB_ATTR_POSITION,
    GLB_ATTR_NORMAL,
    GLB_ATTR_TEXCOORD_0,
};

enum MeshDataType : uint16_t
{
    GLB_TYPE_UNDEFINED,
    GLB_TYPE_SCALAR,
    GLB_TYPE_VEC2,
    GLB_TYPE_VEC3,
    GLB_TYPE_VEC4,
    GLB_TYPE_MAT2,
    GLB_TYPE_MAT3,
    GLB_TYPE_MAT4,
};

struct GLBHeader
{
    uint32_t magic;
    uint32_t version;
    uint32_t length;
};

struct ChunkHeader
{
    uint32_t length;
    uint32_t type = 0;
};

struct NodeDetail
{
    uint32_t    meshIndex;
    std::string name;
    glm::mat4   locTrans;
};

struct GLBBufferView
{
    MeshAttribute attribute;
    MeshDataType  type;
    char*         ptr;
    uint32_t      length;
    uint32_t      count;
};

class GLTFFailedToOpen : public std::bad_exception
{
public:
    GLTFFailedToOpen( const char* path )
    { std::snprintf(m_msg, 128, "Failed to load %s, file is in bad GLB format or not exist.", path ); }
    const char* what() const noexcept override{
        return m_msg;
    }
private:
    char m_msg[128];
};

class GLTFVersionDismatch : public std::bad_exception
{
public:
    GLTFVersionDismatch( const char* path )
    { std::snprintf(m_msg, 128, "Failed to load %s, file version does not match.", path ); }
    const char* what() const noexcept override{
        return m_msg;
    }
private:
    char m_msg[128];
};

class GLTFBadChunk : public std::bad_exception
{
public:
GLTFBadChunk( const char* path )
    { std::snprintf(m_msg, 128, "Failed to load %s, parser reached bad chunk type.", path ); }
    const char* what() const noexcept override{
        return m_msg;
    }
private:
    char m_msg[128];
};

class GLBReader 
{
public:
    GLBReader();
    ~GLBReader();
    void ReadGLBFile( std::string path );
    void GetNodeDetail( const nlohmann::json& nodeInfo, NodeDetail& res );
    void FetchNodeMeshProp( const nlohmann::json& meshInfo, GLBBufferView* bufferView, const uint32_t viewCnt, const uint32_t submesh = 0 );

    inline uint32_t GetSceneCnt(){ return m_jsonChunk["scenes"].size(); };
    inline uint32_t GetNodeCnt( uint32_t sceneIndex ){ return m_jsonChunk["scenes"][sceneIndex]["nodes"].size(); }
    inline uint32_t GetNodeChildrenCnt( const nlohmann::json& nodeInfo ){
        auto iter = nodeInfo.find("children");
        return iter == nodeInfo.end() ? 0 : iter->size();
    }
    inline const nlohmann::json& GetNodeIndicies( uint32_t sceneIndex ){ return m_jsonChunk["scenes"][sceneIndex]["nodes"]; }
    inline const nlohmann::json& GetNodeInfo( uint32_t nodeIndex ){ return m_jsonChunk["nodes"][nodeIndex]; }
    inline const nlohmann::json& GetNodeChildren( const nlohmann::json& nodeInfo ){ return nodeInfo["children"]; }

    inline const nlohmann::json& GetMeshInfo( uint32_t meshIndex ){ return m_jsonChunk["meshes"][meshIndex]; }
    inline uint32_t GetSubmeshCnt( const nlohmann::json& meshInfo ){ return meshInfo["primitives"].size(); }


private:
    inline glm::vec3 M_ReadJsonVec3( const nlohmann::json& a ){ return glm::vec3( a[0], a[1], a[2] ); };
    inline glm::quat M_ReadJsonQuat( const nlohmann::json& a ){ return glm::quat( a[3], a[0], a[1], a[2] ); };
    inline MeshDataType M_MapingGLBDataType( const std::string& str )
    {
        if( str == "SCALAR" ){ return MeshDataType::GLB_TYPE_SCALAR; }
        else if( str == "VEC2" ){ return MeshDataType::GLB_TYPE_VEC2; }
        else if( str == "VEC3" ){ return MeshDataType::GLB_TYPE_VEC3; }
        else if( str == "VEC4" ){ return MeshDataType::GLB_TYPE_VEC4; }
        else if( str == "MAT2" ){ return MeshDataType::GLB_TYPE_MAT2; }
        else if( str == "MAT3" ){ return MeshDataType::GLB_TYPE_MAT3; } 
        else if( str == "MAT4" ){ return MeshDataType::GLB_TYPE_MAT4; }
        return MeshDataType::GLB_TYPE_UNDEFINED;
    }
    bool M_ReadAChunk( const ChunkHeader& chunkHeader, std::fstream& fs );

    nlohmann::json m_jsonChunk;
    uint32_t m_bufferLen;
    char* m_bufferChunk;
};
